package com.neu.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.neu.base.AuthContants;
import com.neu.base.Result;
import com.neu.entity.security.LoginUser;
import com.neu.service.AuthenticationService;
import com.neu.utils.JwtUtils;
import com.neu.utils.RedisUtil;
import com.neu.utils.ServletUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Slf4j
public class UsernamePasswordFilter extends UsernamePasswordAuthenticationFilter {

    private RedisUtil redisUtil;
    private JwtUtils jwtUtils;
    private AuthenticationService authenticationService;

    public UsernamePasswordFilter(AuthenticationManager authenticationManager, RedisUtil redisUtil, JwtUtils jwtUtils, AuthenticationService authenticationService) {
        this.redisUtil = redisUtil;
        this.jwtUtils = jwtUtils;
        this.authenticationService = authenticationService;
        super.setAuthenticationManager(authenticationManager);
        super.setPostOnly(true);//只支持post请求
        super.setFilterProcessesUrl("/auth/login");//设置登录的url
        super.setUsernameParameter("username");
        super.setPasswordParameter("password");
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        //2.获取用户名和密码 从请求体中拿出来 使用ObjectMapper进行反序列化
        try {
            LoginUser user = new ObjectMapper().readValue(request.getInputStream(), LoginUser.class);
            String username = user.getUsername();
            String password = user.getPassword();
            String ipAddress = getClientIP(request); // 获取客户端IP地址
            log.info("用户名: {}, IP: {}", username, ipAddress);
            //将用户名和 IP 地址存储到请求属性中，且确保在任何可能抛出异常之前进行存储。
            // 将用户名和IP存储到请求属性中，供后续方法使用
            request.setAttribute("username", username);
            request.setAttribute("ipAddress", ipAddress);
            // 检查是否被锁定
            if (isLocked(username, ipAddress)) {
                throw new LockedException("密码错误5次，请" + AuthContants.LOCK_TIME_DURATION / 60 + "分钟后再登录");
            }
            //将请求信息封装到Authentication对象  principal: 用户信息   credentials: 密码   authorities: 权限
            //[Principal=888, Credentials=[PROTECTED], Authenticated=false, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[]]
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            setDetails(request, authRequest);

            // 调用AuthenticationManager的authenticate方法 进行身份验证
            return this.getAuthenticationManager().authenticate(authRequest);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取客户端IP地址
     */
    private String getClientIP(HttpServletRequest request) {
        String xfHeader = request.getHeader("X-Forwarded-For");
        if (xfHeader == null) {
            return request.getRemoteAddr();
        }
        return xfHeader.split(",")[0];
    }

    /**
     * 检查用户是否被锁定
     */
    private boolean isLocked(String username, String ipAddress) {
        String userRedisKey = AuthContants.USERLOGINTIMES + username;
        String ipRedisKey = AuthContants.USERLOGINTIMES_IP + ipAddress;

        String userAttemptsStr = redisUtil.getString(userRedisKey);
        int userAttempts = userAttemptsStr == null ? 0 : Integer.parseInt(userAttemptsStr);

        String ipAttemptsStr = redisUtil.getString(ipRedisKey);
        int ipAttempts = ipAttemptsStr == null ? 0 : Integer.parseInt(ipAttemptsStr);

        return userAttempts >= AuthContants.MAX_ATTEMPTS || ipAttempts >= AuthContants.MAX_ATTEMPTS;
    }

    /**
     * 登录成功后的处理 此时再生成token并存入redis
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {

       /* //获取用户信息 从Authentication对象中获取
        LoginUser userPrincipal = (LoginUser) authResult.getPrincipal();
        String username = userPrincipal.getUsername();*/
        // 从请求属性中获取用户名和IP地址
        String username = (String) request.getAttribute("username");
        String ipAddress = (String) request.getAttribute("ipAddress");
        //清除登录失败次数
        redisUtil.delete(AuthContants.USERLOGINTIMES + username);
        redisUtil.delete(AuthContants.USERLOGINTIMES_IP + ipAddress);
        log.info("用户 {} 登录成功，清除失败计数", username);
        //获取完整的用户信息
        LoginUser loginUser = authenticationService.login(username);
        String token = jwtUtils.generateToken(username);
        log.info("生成的token: " + token);
        //将token放入响应头中
        response.setHeader(AuthContants.TOKEN_HEADER, token);
        //将token存入redis
        redisUtil.set(AuthContants.REDIS_KEY_AUTH_TOKEN + username, token, jwtUtils.getExpiration());
        //将用户信息存入redis
        redisUtil.set(AuthContants.REDIS_KEY_AUTH_USER_DETAIL + username, loginUser, jwtUtils.getExpiration());
        //将用户信息存入上下文 principal: 用户信息   credentials: 密码   authorities: 权限
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, loginUser.getPassword(), loginUser.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        log.info("存储的上下文" + SecurityContextHolder.getContext().getAuthentication().toString());
        //返回成功信息
        ServletUtils.renderResult(response, Result.success(token));

    }

    /**
     * 如果 attemptAuthentication 抛出 AuthenticationException 则会调用这个方法
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                                              AuthenticationException failed) throws IOException {
        // 从请求属性中获取用户名和IP地址
        String username = (String) request.getAttribute("username");
        String ipAddress = (String) request.getAttribute("ipAddress");
        if (username != null && ipAddress != null) {
            String userRedisKey = AuthContants.USERLOGINTIMES + username;
            String ipRedisKey = AuthContants.USERLOGINTIMES_IP + ipAddress;

            if (!(failed instanceof LockedException)) {
                // 仅在不是 LockedException 的情况下增加登录失败次数
                // 增加用户登录失败次数
                int userAttempts = incrementAttempts(userRedisKey);
                // 增加IP登录失败次数
                int ipAttempts = incrementAttempts(ipRedisKey);

                // 使用管道批量增加登录失败次数
            /*Map<String, String> attemptsMap = new HashMap<>();
            attemptsMap.put(userRedisKey, String.valueOf(incrementAttempts(userRedisKey)));
            attemptsMap.put(ipRedisKey, String.valueOf(incrementAttempts(ipRedisKey)));

            redisUtil.batchSet(attemptsMap, AuthContants.LOCK_TIME_DURATION, TimeUnit.MILLISECONDS);*/

                log.warn("用户 {} 登录失败次数: {}, IP {} 登录失败次数: {}", username, userAttempts, ipAddress, ipAttempts);
            } else {
                // 对于 LockedException，可以选择记录锁定事件或采取其他措施
                log.warn("用户 {} 已被锁定，IP {}", username, ipAddress);
            }
        }

        ServletUtils.renderResult(response, Result.error(failed.getMessage()));

    }

    /**
     * 增加登录失败次数并设置过期时间
     */
    private int incrementAttempts(String redisKey) {
        String attemptsStr = redisUtil.getString(redisKey);
        int attempts = attemptsStr == null ? 0 : Integer.parseInt(attemptsStr);
        attempts++;
        redisUtil.set(redisKey, String.valueOf(attempts), AuthContants.LOCK_TIME_DURATION);
        return attempts;
    }

}
